"""
文件管理模块

该模块提供文件存储、管理和自动清理功能。
包括将文件保存到指定目录、生成唯一文件名、定期清理过期文件等功能。
"""

import os
import shutil
import logging
from pathlib import Path
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Tuple
import hashlib
import json
from concurrent.futures import ThreadPoolExecutor
import threading
import time

from config import STORAGE_CONFIG, CLEANUP_CONFIG, LOGGING_CONFIG

# 配置日志
logging.basicConfig(
    level=getattr(logging, LOGGING_CONFIG["level"]),
    format=LOGGING_CONFIG["format"],
    handlers=[
        logging.FileHandler(LOGGING_CONFIG["log_file"]),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)


class FileManager:
    """
    文件管理器类

    提供文件存储、管理和自动清理功能。
    支持按类型分类存储文件，并提供定时清理过期文件的功能。

    Attributes:
        base_dir: 基础存储目录
        subdirs: 子目录映射
        cleanup_thread: 清理线程
        executor: 线程池执行器
    """

    def __init__(self, base_dir: Optional[Path] = None):
        """
        初始化文件管理器

        Args:
            base_dir: 基础存储目录，如果为None则使用配置中的默认路径
        """
        self.base_dir = base_dir or STORAGE_CONFIG["base_dir"]
        self.subdirs = STORAGE_CONFIG["subdirs"]
        self.cleanup_thread = None
        self.executor = ThreadPoolExecutor(max_workers=4)

        # 确保所有必要的目录存在
        self._ensure_directories()

        # 启动自动清理线程
        if CLEANUP_CONFIG["auto_cleanup_enabled"]:
            self.start_cleanup_scheduler()

    def _ensure_directories(self):
        """确保所有必要的目录都存在"""
        try:
            self.base_dir.mkdir(parents=True, exist_ok=True)
            for subdir_name in self.subdirs.values():
                subdir_path = self.base_dir / subdir_name
                subdir_path.mkdir(parents=True, exist_ok=True)
            logger.info("所有存储目录已准备就绪")
        except Exception as e:
            logger.error(f"创建目录失败: {e}")
            raise

    def generate_filename(self, original_name: str, size: str, suffix: str) -> str:
        """
        生成唯一文件名

        Args:
            original_name: 原始文件名（不含扩展名）
            size: 尺寸标识
            suffix: 文件类型后缀（如standard, hd等）

        Returns:
            生成的唯一文件名
        """
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        # 清理文件名中的特殊字符
        clean_name = "".join(c for c in original_name if c.isalnum() or c in (' ', '-', '_')).rstrip()
        filename = STORAGE_CONFIG["filename_format"].format(
            timestamp=timestamp,
            original_name=clean_name[:20],  # 限制原始文件名长度
            size=size,
            suffix=suffix
        )
        return f"{filename}.png"  # 统一使用PNG格式

    def get_file_hash(self, file_path: Path) -> str:
        """
        计算文件哈希值

        Args:
            file_path: 文件路径

        Returns:
            文件的MD5哈希值
        """
        try:
            with open(file_path, 'rb') as f:
                return hashlib.md5(f.read()).hexdigest()
        except Exception as e:
            logger.error(f"计算文件哈希失败: {e}")
            return ""

    def save_file(self, source_path: str, file_type: str, size: str, original_name: str, openid: str = None) -> Dict[str, str]:
        """
        保存文件到指定目录

        Args:
            source_path: 源文件路径
            file_type: 文件类型（standard, hd, transparent, layout, social_media）
            size: 尺寸标识
            original_name: 原始文件名
            openid: 用户openid，用于按用户分目录存储

        Returns:
            包含保存信息的字典
            {
                'original_path': 原始路径,
                'saved_path': 保存路径,
                'filename': 文件名,
                'file_size': 文件大小（字节）,
                'created_at': 创建时间,
                'delete_at': 预计删除时间
            }
        """
        try:
            if not os.path.exists(source_path):
                raise FileNotFoundError(f"源文件不存在: {source_path}")

            # 获取子目录
            if file_type not in self.subdirs:
                raise ValueError(f"无效的文件类型: {file_type}")

            subdir = self.subdirs[file_type]
            
            # 根据openid创建用户子目录
            if openid:
                target_dir = self.base_dir / openid / subdir
                target_dir.mkdir(parents=True, exist_ok=True)
            else:
                target_dir = self.base_dir / subdir

            # 生成文件名
            filename = self.generate_filename(
                Path(original_name).stem,
                size,
                file_type
            )
            target_path = target_dir / filename

            # 如果文件已存在，先删除旧文件及其元数据
            if target_path.exists():
                self.delete_file(str(target_path))

            # 复制文件
            shutil.copy2(source_path, target_path)

            # 获取文件信息
            file_size = target_path.stat().st_size
            created_at = datetime.fromtimestamp(target_path.stat().st_ctime)

            # 计算删除日期（7天后）
            from datetime import timedelta
            delete_at = created_at + timedelta(days=7)
            
            # 记录文件元数据
            metadata = {
                'original_path': source_path,
                'saved_path': str(target_path),
                'filename': filename,
                'file_size': file_size,
                'created_at': created_at.isoformat(),
                'delete_at': delete_at.isoformat(),
                'file_hash': self.get_file_hash(target_path),
                'file_type': file_type,
                'size': size
            }

            # 保存元数据
            metadata_file = target_path.with_suffix('.json')
            with open(metadata_file, 'w', encoding='utf-8') as f:
                json.dump(metadata, f, ensure_ascii=False, indent=2)

            logger.info(f"文件已保存: {target_path}")
            return metadata

        except Exception as e:
            logger.error(f"保存文件失败: {e}")
            raise

    def get_file_info(self, file_path: str) -> Optional[Dict[str, Any]]:
        """
        获取文件信息

        Args:
            file_path: 文件路径

        Returns:
            文件信息字典，如果文件不存在则返回None
        """
        try:
            path = Path(file_path)
            if not path.exists():
                return None

            stat = path.stat()
            return {
                'path': str(path),
                'filename': path.name,
                'size': stat.st_size,
                'created_at': datetime.fromtimestamp(stat.st_ctime).isoformat(),
                'modified_at': datetime.fromtimestamp(stat.st_mtime).isoformat(),
                'file_type': path.suffix.lower()
            }
        except Exception as e:
            logger.error(f"获取文件信息失败: {e}")
            return None

    def list_files(self, file_type: Optional[str] = None, openid: str = None) -> List[Dict[str, Any]]:
        """
        列出存储的文件

        Args:
            file_type: 文件类型过滤，如果为None则列出所有文件
            openid: 用户openid，如果提供则只列出该用户的文件

        Returns:
            文件信息列表
        """
        try:
            files = []

            # 如果提供了openid，只在对应用户目录中查找
            if openid:
                user_base_dir = self.base_dir / openid
                if not user_base_dir.exists():
                    return []
                
                if file_type and file_type in self.subdirs:
                    # 只列出指定类型的文件
                    target_dir = user_base_dir / self.subdirs[file_type]
                    if target_dir.exists():
                        for file_path in target_dir.glob('*.png'):
                            info = self.get_file_info(str(file_path))
                            if info:
                                info['openid'] = openid  # 添加openid信息
                                files.append(info)
                else:
                    # 列出所有文件
                    for subdir_name in self.subdirs.values():
                        target_dir = user_base_dir / subdir_name
                        if target_dir.exists():
                            for file_path in target_dir.glob('*.png'):
                                info = self.get_file_info(str(file_path))
                                if info:
                                    info['openid'] = openid  # 添加openid信息
                                    files.append(info)
            else:
                # 如果没有提供openid，遍历所有用户目录
                if self.base_dir.exists():
                    for user_dir in self.base_dir.iterdir():
                        if user_dir.is_dir() and not user_dir.name.startswith('.'):
                            current_openid = user_dir.name
                            
                            if file_type and file_type in self.subdirs:
                                target_dir = user_dir / self.subdirs[file_type]
                                if target_dir.exists():
                                    for file_path in target_dir.glob('*.png'):
                                        info = self.get_file_info(str(file_path))
                                        if info:
                                            info['openid'] = current_openid
                                            files.append(info)
                            else:
                                for subdir_name in self.subdirs.values():
                                    target_dir = user_dir / subdir_name
                                    if target_dir.exists():
                                        for file_path in target_dir.glob('*.png'):
                                            info = self.get_file_info(str(file_path))
                                            if info:
                                                info['openid'] = current_openid
                                                files.append(info)

            # 按创建时间排序
            files.sort(key=lambda x: x['created_at'], reverse=True)
            return files

        except Exception as e:
            logger.error(f"列出文件失败: {e}")
            return []

    def delete_file(self, file_path: str) -> bool:
        """
        删除文件及其元数据

        Args:
            file_path: 文件路径

        Returns:
            是否删除成功
        """
        try:
            path = Path(file_path)
            if not path.exists():
                return False

            # 删除文件
            path.unlink()

            # 删除元数据文件
            metadata_file = path.with_suffix('.json')
            if metadata_file.exists():
                metadata_file.unlink()

            logger.info(f"文件已删除: {file_path}")
            return True

        except Exception as e:
            logger.error(f"删除文件失败: {e}")
            return False

    def cleanup_old_files(self, days: Optional[int] = None) -> Dict[str, int]:
        """
        清理过期文件

        Args:
            days: 保留天数，如果为None则使用配置中的默认值

        Returns:
            清理统计信息
            {
                'total_files': 总文件数,
                'deleted_files': 已删除文件数,
                'freed_space': 释放空间（字节）
            }
        """
        try:
            retention_days = days or CLEANUP_CONFIG["retention_days"]
            cutoff_date = datetime.now() - timedelta(days=retention_days)

            total_files = 0
            deleted_files = 0
            freed_space = 0

            for subdir_name in self.subdirs.values():
                target_dir = self.base_dir / subdir_name
                if not target_dir.exists():
                    continue

                for file_path in target_dir.glob('*.png'):
                    total_files += 1

                    try:
                        stat = file_path.stat()
                        created_at = datetime.fromtimestamp(stat.st_ctime)

                        if created_at < cutoff_date:
                            freed_space += stat.st_size
                            if self.delete_file(str(file_path)):
                                deleted_files += 1

                    except Exception as e:
                        logger.error(f"处理文件失败 {file_path}: {e}")

            result = {
                'total_files': total_files,
                'deleted_files': deleted_files,
                'freed_space': freed_space,
                'cleanup_date': datetime.now().isoformat()
            }

            # 记录清理日志
            self._log_cleanup(result)

            logger.info(f"清理完成: 删除了 {deleted_files} 个文件，释放 {freed_space} 字节空间")
            return result

        except Exception as e:
            logger.error(f"清理文件失败: {e}")
            return {'total_files': 0, 'deleted_files': 0, 'freed_space': 0}

    def _log_cleanup(self, cleanup_result: Dict[str, Any]):
        """记录清理日志"""
        try:
            log_file = CLEANUP_CONFIG["cleanup_log"]
            log_file.parent.mkdir(parents=True, exist_ok=True)

            with open(log_file, 'a', encoding='utf-8') as f:
                f.write(f"{json.dumps(cleanup_result, ensure_ascii=False)}\n")
        except Exception as e:
            logger.error(f"记录清理日志失败: {e}")

    def start_cleanup_scheduler(self):
        """启动自动清理调度器"""
        if self.cleanup_thread and self.cleanup_thread.is_alive():
            logger.warning("清理调度器已在运行")
            return

        def cleanup_worker():
            """清理工作线程"""
            while True:
                try:
                    interval_hours = CLEANUP_CONFIG["cleanup_interval"]
                    time.sleep(interval_hours * 3600)  # 转换为秒

                    logger.info("开始执行定期清理任务")
                    result = self.cleanup_old_files()
                    logger.info(f"定期清理完成: {result}")

                except Exception as e:
                    logger.error(f"清理任务执行失败: {e}")

        self.cleanup_thread = threading.Thread(target=cleanup_worker, daemon=True)
        self.cleanup_thread.start()
        logger.info("自动清理调度器已启动")

    def stop_cleanup_scheduler(self):
        """停止自动清理调度器"""
        # 注意：由于线程是守护线程，无法强制停止
        # 这里只是记录状态，实际会在程序退出时停止
        logger.info("自动清理调度器停止请求已发送")

    def get_storage_stats(self) -> Dict[str, Any]:
        """
        获取存储统计信息

        Returns:
            存储统计信息字典
        """
        try:
            stats = {
                'base_dir': str(self.base_dir),
                'total_files': 0,
                'total_size': 0,
                'directories': {},
                'last_cleanup': None
            }

            for file_type, subdir_name in self.subdirs.items():
                target_dir = self.base_dir / subdir_name
                if not target_dir.exists():
                    continue

                files = list(target_dir.glob('*.png'))
                total_size = sum(f.stat().st_size for f in files)

                stats['directories'][file_type] = {
                    'path': str(target_dir),
                    'file_count': len(files),
                    'total_size': total_size
                }

                stats['total_files'] += len(files)
                stats['total_size'] += total_size

            # 获取最后清理时间
            log_file = CLEANUP_CONFIG["cleanup_log"]
            if log_file.exists():
                try:
                    with open(log_file, 'r', encoding='utf-8') as f:
                        lines = f.readlines()
                        if lines:
                            last_line = lines[-1]
                            last_cleanup = json.loads(last_line)
                            stats['last_cleanup'] = last_cleanup
                except Exception as e:
                    logger.error(f"读取清理日志失败: {e}")

            return stats

        except Exception as e:
            logger.error(f"获取存储统计信息失败: {e}")
            return {}

    def get_user_stats(self, user_dir: str) -> Dict[str, Any]:
        """
        获取用户统计信息

        Args:
            user_dir: 用户目录路径

        Returns:
            用户统计信息
        """
        try:
            user_path = Path(user_dir)
            if not user_path.exists():
                return {
                    'stats': {
                        'totalPhotos': 0,
                        'thisMonth': 0
                    },
                    'storage': {
                        'totalFiles': 0,
                        'totalSize': '0 KB',
                        'standardCount': 0,
                        'hdCount': 0
                    }
                }

            total_files = 0
            total_size = 0
            standard_count = 0
            hd_count = 0
            this_month_count = 0

            current_month = datetime.now().strftime('%Y-%m')

            for file_type, subdir_name in self.subdirs.items():
                target_dir = user_path / subdir_name
                if not target_dir.exists():
                    continue

                for file_path in target_dir.glob('*.png'):
                    total_files += 1
                    file_size = file_path.stat().st_size
                    total_size += file_size

                    if file_type == 'standard':
                        standard_count += 1
                    elif file_type == 'hd':
                        hd_count += 1

                    # 检查是否为本月创建
                    created_at = datetime.fromtimestamp(file_path.stat().st_ctime)
                    if created_at.strftime('%Y-%m') == current_month:
                        this_month_count += 1

            # 格式化文件大小
            if total_size < 1024:
                formatted_size = f"{total_size} B"
            elif total_size < 1024 * 1024:
                formatted_size = f"{total_size / 1024:.1f} KB"
            else:
                formatted_size = f"{total_size / (1024 * 1024):.1f} MB"

            return {
                'stats': {
                    'totalPhotos': total_files,
                    'thisMonth': this_month_count
                },
                'storage': {
                    'totalFiles': total_files,
                    'totalSize': formatted_size,
                    'standardCount': standard_count,
                    'hdCount': hd_count
                }
            }

        except Exception as e:
            logger.error(f"获取用户统计信息失败: {e}")
            return {
                'stats': {
                    'totalPhotos': 0,
                    'thisMonth': 0
                },
                'storage': {
                    'totalFiles': 0,
                    'totalSize': '0 KB',
                    'standardCount': 0,
                    'hdCount': 0
                }
            }

    def get_storage_info(self, user_dir: str) -> Dict[str, Any]:
        """
        获取存储信息

        Args:
            user_dir: 用户目录路径

        Returns:
            存储信息
        """
        return self.get_user_stats(user_dir)

    def list_user_files(self, user_dir: str, file_type: Optional[str] = None, limit: int = 50, offset: int = 0) -> List[Dict[str, Any]]:
        """
        列出用户文件

        Args:
            user_dir: 用户目录路径
            file_type: 文件类型过滤
            limit: 返回数量限制
            offset: 偏移量

        Returns:
            文件信息列表
        """
        try:
            files = []
            user_path = Path(user_dir)
            
            if not user_path.exists():
                return files

            # 如果指定了文件类型
            if file_type and file_type in self.subdirs:
                target_dir = user_path / self.subdirs[file_type]
                if target_dir.exists():
                    all_files = list(target_dir.glob('*.png'))
                    # 按创建时间排序，最新的在前
                    all_files.sort(key=lambda x: x.stat().st_ctime, reverse=True)
                    
                    # 应用分页
                    paginated_files = all_files[offset:offset + limit]
                    
                    for file_path in paginated_files:
                        info = self.get_file_info(str(file_path))
                        if info:
                            info.update({
                                'url': f"/static/{user_path.name}/{self.subdirs[file_type]}/{file_path.name}",
                                'file_type': file_type,
                                'size_name': self._extract_size_from_filename(file_path.name),
                                'background_color': self._extract_color_from_filename(file_path.name)
                            })
                            files.append(info)
            else:
                # 列出所有类型的文件
                all_files = []
                for ft, subdir_name in self.subdirs.items():
                    target_dir = user_path / subdir_name
                    if target_dir.exists():
                        for file_path in target_dir.glob('*.png'):
                            info = self.get_file_info(str(file_path))
                            if info:
                                info.update({
                                    'url': f"/static/{user_path.name}/{subdir_name}/{file_path.name}",
                                    'file_type': ft,
                                    'size_name': self._extract_size_from_filename(file_path.name),
                                    'background_color': self._extract_color_from_filename(file_path.name)
                                })
                                all_files.append(info)
                
                # 按创建时间排序
                all_files.sort(key=lambda x: x['created_at'], reverse=True)
                
                # 应用分页
                files = all_files[offset:offset + limit]

            return files

        except Exception as e:
            logger.error(f"列出用户文件失败: {e}")
            return []

    def _extract_size_from_filename(self, filename: str) -> str:
        """从文件名中提取尺寸信息"""
        try:
            # 假设文件名格式: 20250804_173607_原始名_尺寸_类型.png
            parts = filename.split('_')
            if len(parts) >= 3:
                return parts[-2]  # 倒数第二部分是尺寸
            return "未知"
        except:
            return "未知"

    def _extract_color_from_filename(self, filename: str) -> str:
        """从文件名中提取背景颜色信息"""
        try:
            # 从元数据文件中获取颜色信息
            json_file = filename.replace('.png', '.json')
            json_path = Path(self.base_dir) / "**" / json_file
            
            for file in Path(self.base_dir).rglob(json_file):
                if file.exists():
                    with open(file, 'r', encoding='utf-8') as f:
                        metadata = json.load(f)
                        return metadata.get('background_color', '未知')
            return "未知"
        except:
            return "未知"

    def __del__(self):
        """析构函数"""
        if hasattr(self, 'executor'):
            self.executor.shutdown(wait=False)
        if hasattr(self, 'cleanup_thread'):
            logger.info("文件管理器正在关闭...")